#ifndef LIBUCW_MSGPACK_H
#define LIBUCW_MSGPACK_H

//
// MsgPack Format Specification
//
// http://wiki.msgpack.org/display/MSGPACK/Format+specification
//

#include <stdio.h>

typedef enum MSGPACK_TYPE
{
    MSGPACK_TYPE_NIL = 0,
    MSGPACK_TYPE_BOOLEAN,
    MSGPACK_TYPE_SIGNED,
    MSGPACK_TYPE_UNSIGNED,
    MSGPACK_TYPE_FLOAT,
    MSGPACK_TYPE_RAW,
    MSGPACK_TYPE_ARRAY,
    MSGPACK_TYPE_MAP,
    MSGPACK_TYPE_RESERVED
} MSGPACK_TYPE;

typedef struct MSGPACK_OBJECT
{
    MSGPACK_TYPE Type;

    union
    {
        char Boolean;
        long long I64;
        unsigned long long U64;
        double Dec;

        struct
        {
            const void *Pointer;
            unsigned int Length;
        } Raw;

        struct
        {
            unsigned int Length;
        } Array;

        struct
        {
            unsigned int Length;
        } Map;

    } Value;

} MSGPACK_OBJECT;

#define MsgpackObjectType(_o)           ((_o)->Type)

#define MsgpackIsNil(_o)                ((_o)->Type == MSGPACK_TYPE_NIL)
#define MsgpackSetNil(_o)               do { \
    (_o)->Type = MSGPACK_TYPE_NIL; \
    } while (0)

#define MsgpackIsBoolean(_o)            ((_o)->Type == MSGPACK_TYPE_BOOLEAN)
#define MsgpackGetBoolean(_o)           ((_o)->Value.Boolean)
#define MsgpackSetBoolean(_o, _v)       do { \
    (_o)->Type = MSGPACK_TYPE_BOOLEAN; \
    (_o)->Value.Boolean = (_v); \
    } while (0)

#define MsgpackIsSigned(_o)             ((_o)->Type == MSGPACK_TYPE_SIGNED)
#define MsgpackGetSigned(_o)            ((_o)->Value.I64)
#define MsgpackSetSigned(_o, _v)        do { \
    (_o)->Type = MSGPACK_TYPE_SIGNED; \
    (_o)->Value.I64 = (_v); \
    } while (0)

#define MsgpackIsUnsigned(_o)           ((_o)->Type == MSGPACK_TYPE_UNSIGNED)
#define MsgpackGetUnsigned(_o)          ((_o)->Value.U64)
#define MsgpackSetUnsigned(_o, _v)      do { \
    (_o)->Type = MSGPACK_TYPE_UNSIGNED; \
    (_o)->Value.U64 = (_v); \
    } while (0)

#define MsgpackIsFloat(_o)              ((_o)->Type == MSGPACK_TYPE_FLOAT)
#define MsgpackGetFloat(_o)             ((_o)->Value.Dec)
#define MsgpackSetFloat(_o, _v)         do { \
    (_o)->Type = MSGPACK_TYPE_FLOAT; \
    (_o)->Value.Dec = (_v); \
    } while (0)

#define MsgpackIsRaw(_o)                ((_o)->Type == MSGPACK_TYPE_RAW)
#define MsgpackGetRawLength(_o)         ((_o)->Value.Raw.Length)
#define MsgpackGetRawPointer(_o)        ((_o)->Value.Raw.Pointer)
#define MsgpackSetRaw(_o, _v, _l)       do { \
    (_o)->Type = MSGPACK_TYPE_RAW; \
    (_o)->Value.Raw.Length = (_l); \
    (_o)->Value.Raw.Pointer = (_v); \
    } while (0)

#define MsgpackIsArray(_o)              ((_o)->Type == MSGPACK_TYPE_ARRAY)
#define MsgpackGetArrayLength(_o)       ((_o)->Value.Array.Length)
#define MsgpackSetArray(_o, _l)         do { \
    (_o)->Type = MSGPACK_TYPE_ARRAY; \
    (_o)->Value.Array.Length = (_l); \
    } while (0)

#define MsgpackIsMap(_o)                ((_o)->Type == MSGPACK_TYPE_MAP)
#define MsgpackGetMapLength(_o)         ((_o)->Value.Map.Length)
#define MsgpackSetMap(_o, _l)           do { \
    (_o)->Type = MSGPACK_TYPE_MAP; \
    (_o)->Value.Map.Length = (_l); \
    } while (0)


typedef struct MSGPACK_PACKER MSGPACK_PACKER;
typedef struct MSGPACK_UNPACKER MSGPACK_UNPACKER;

MSGPACK_PACKER *MsgpackPackerNew();
void MsgpackPackerPrepare(MSGPACK_PACKER *Packer);
void MsgpackPackerPack(MSGPACK_PACKER *Packer, const MSGPACK_OBJECT Object[], size_t Count);
void MsgpackPackerFinalize(MSGPACK_PACKER *Packer);
size_t MsgpackPackerGetLength(MSGPACK_PACKER *Packer);
unsigned char *MsgpackPackerRead(MSGPACK_PACKER *Packer, size_t Count);
unsigned char *MsgpackPackerPeek(MSGPACK_PACKER *Packer);
void MsgpackPackerFree(MSGPACK_PACKER *Packer);

MSGPACK_UNPACKER *MsgpackUnpackerNew();
void *MsgpackUnpackerBufferGet(MSGPACK_UNPACKER *Unpacker);
void MsgpackUnpackerBufferReserve(MSGPACK_UNPACKER *Unpacker, size_t Size);
void MsgpackUnpackerBufferConsume(MSGPACK_UNPACKER *Unpacker, size_t Size);
int MsgpackUnpackerPrepare(MSGPACK_UNPACKER *Unpacker);
size_t MsgpackUnpackerObjects(MSGPACK_UNPACKER *Unpacker);
size_t MsgpackUnpackerUnpack(MSGPACK_UNPACKER *Unpacker, MSGPACK_OBJECT Object[], size_t Count);
void MsgpackUnpackerFinalize(MSGPACK_UNPACKER *Unpacker);
void MsgpackUnpackerFree(MSGPACK_UNPACKER *Unpacker);

void MsgpackObjectDump(const MSGPACK_OBJECT *o);

#endif // LIBUCW_MSGPACK_H
